package pl.jad.database.table.column;

import java.lang.reflect.Field;
import java.sql.Types;
import java.util.logging.Logger;

import pl.jad.database.mapper.FieldTypeAnnotation;
import pl.jad.database.table.Table;

public abstract class TableColumn implements TableColumnInterface {

	protected Logger _Log;
	
	protected String _name;
	
	protected Field _field;
	
	protected Table _table;
	
	/**
	 * SQL type of column
	 */
	protected int _type;
	
	protected String _annotationType;
	
	public TableColumn(Table table, Field field) {
		this._Log = Logger.getLogger(this.getClass().getName());
		
		this._name = field.getName();
		this._type = getType(field);
		this._table = table;
		this._field = field;
	}
	
	@Override
	public String getName() {
		return this._name;
	}

	@Override
	public String getSqlType() {
		_Log.info("Checking SQL type for " + getName() + " & " + getType());
		
		if (hasAnnotationType()) {
			try {
				return getAnnotationType();
			} catch (Exception e) {
				// use default type for that kind of class
				e.printStackTrace();
			}
		}
		
		switch (getType()) {
			case Types.INTEGER :
				return "INT";
			case Types.VARCHAR :
				// FIXME: 100?
				return "VARCHAR(100)";
			case Types.LONGVARCHAR :
				return "TEXT";
			case Types.DOUBLE :
				return "DOUBLE";
			case Types.FLOAT :
				return "FLOAT";
			case Types.BOOLEAN :
				return "BOOLEAN";
			case Types.BLOB :
				return "BLOB";
			case Types.CHAR :
				return "CHAR";
			case Types.BIGINT :
				return "BIGINT";
			default : // TODO: figure what should be returned here
				return "TEXT";
		}
	}
	
	protected boolean hasAnnotationType() {
		return ((this._annotationType != null) || 
				(this._field.isAnnotationPresent(FieldTypeAnnotation.class)));
	}
	
	protected String getAnnotationType() throws Exception {
		if (hasAnnotationType()) {
			if (this._annotationType == null && 
					this._field.isAnnotationPresent(FieldTypeAnnotation.class)) {
				FieldTypeAnnotation annotation = _field.getAnnotation(FieldTypeAnnotation.class);
				this._annotationType = annotation.value();
			}
			return this._annotationType;
		} else {
			throw new Exception("Field does not have SQL type specified in annotation");
		}
	}
	
	protected int getType(Field field) {
		if (field.isAnnotationPresent(FieldTypeAnnotation.class)) {
			FieldTypeAnnotation annotation = field.getAnnotation(FieldTypeAnnotation.class);
			_Log.info(field.getName() + " has SQL type specified by annotation: " 
					+ annotation.value());
			return 1;
		} else {
			return getType(field.getType());
		}
	}

	protected int getType(Class<?> Cla) {	
		_Log.info("Looking for SQL type for class " + Cla.getName());
		
		if (Cla == int.class) {
			return Types.INTEGER;
		} else if (Cla == "".getClass()) {
			return Types.LONGVARCHAR;
		} else if (Cla == double.class) {
			return Types.DOUBLE;
		} else if (Cla == float.class) {
			return Types.FLOAT;
		} else if (Cla == boolean.class) {
			return Types.BOOLEAN;
		} else if (Cla == byte.class) {
			return Types.BLOB;
		} else if (Cla == char.class) {
			return Types.CHAR;
		} else if (Cla == short.class) {
			return Types.SMALLINT;
		} else if (Cla == long.class) {
			return Types.BIGINT;	
	    } else {
			return Types.JAVA_OBJECT;
		}
	}

	@Override
	public int getType() {
		return this._type;
	}
	
	protected Field getField() {
		return this._field;
	}

	@Override
	public String getSqlFieldValue(Object object) throws Exception {
		boolean accessible = getField().isAccessible();
		if (!accessible) {
			getField().setAccessible(true);
		}
		
		String value;
		switch (getType()) {
			case Types.INTEGER :
				if (object instanceof Integer) {
					value = object.toString();
				} else {
					value = Integer.toString(getField().getInt(object));
				}
				break;
			case Types.LONGVARCHAR :
				if (object instanceof String) {
					value = "'" + object.toString() + "'";
				} else {
					value = "'" + (String) getField().get(object) + "'";
				}
				break;
			case Types.VARCHAR :
				if (isEmptyField(object)) {
					value = "''";
				} else {
					value = "'" + getField().get(object).toString() + "'";
				}
				break;
			case Types.DOUBLE :
				if (object instanceof Double) {
					value = object.toString();
				} else {
					value = Double.toString(getField().getDouble(object));
				}
				break;
			case Types.FLOAT :
				if (object instanceof Float) {
					value = object.toString();
				} else {
					value = Float.toString(getField().getFloat(object));
				}
				break;
			case Types.BOOLEAN :
				Boolean b;
				if (object instanceof Boolean) {
					b = (Boolean)object;
				} else {
					b = getField().getBoolean(object);
				}
				value = (b.booleanValue()) ? "1" : "0";
				break;
			case Types.BLOB :
				if (object instanceof Byte) {
					value = object.toString();
				} else {
					value = Byte.toString(getField().getByte(object));
				}
				break;
			case Types.CHAR :
				if (object instanceof Character) {
					value = "'" + object.toString() + "'";
				} else {
					value = "'" + getField().get(object).toString() + "'";
				}
				break;
			case Types.BIGINT :
				if (object instanceof Long) {
					value = object.toString();
				} else {
					value = Long.toString(getField().getLong(object));
				}
				break;
			case Types.SMALLINT :
				if (object instanceof Short) {
					value = object.toString();
				} else {
					value = Short.toString(getField().getShort(object));
				}
				break;
			default :
				throw new IllegalArgumentException("Unknown table column type: " 
						+ object.getClass().getName());
		}
		
		if (!accessible) {
			getField().setAccessible(false);
		}
		
		return value;
	}
	
	/**
	 * Checks if field has no value assigned / is null
	 * @param object
	 * @return TRUE if field has no value assigned; FALSE if field has any value assigned
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	protected boolean isEmptyField(Object object) throws IllegalArgumentException, IllegalAccessException {
		if (getField().get(object) == null) {
			return true;
		}
		return false;
	}

}
